"""Utilities for loading skills for V1 conversations.

This module provides functions to load skills from various sources:
- Global skills from OpenHands/skills/
- User skills from ~/.openhands/skills/
- Repository-level skills from the workspace

All skills are used in V1 conversations.
"""

import logging
import os
from pathlib import Path

import openhands
from openhands.app_server.sandbox.sandbox_models import SandboxInfo
from openhands.sdk.context.skills import Skill
from openhands.sdk.workspace.remote.async_remote_workspace import AsyncRemoteWorkspace

_logger = logging.getLogger(__name__)

# Path to global skills directory
GLOBAL_SKILLS_DIR = os.path.join(
    os.path.dirname(os.path.dirname(openhands.__file__)),
    'skills',
)
WORK_HOSTS_SKILL = """The user has access to the following hosts for accessing a web application,
each of which has a corresponding port:"""


def _find_and_load_global_skill_files(skill_dir: Path) -> list[Skill]:
    """Find and load all .md files from the global skills directory.

    Args:
        skill_dir: Path to the global skills directory

    Returns:
        List of Skill objects loaded from the files (excluding README.md)
    """
    skills = []

    try:
        # Find all .md files in the directory (excluding README.md)
        md_files = [f for f in skill_dir.glob('*.md') if f.name.lower() != 'readme.md']

        # Load skills from the found files
        for file_path in md_files:
            try:
                skill = Skill.load(file_path, skill_dir)
                skills.append(skill)
                _logger.debug(f'Loaded global skill: {skill.name} from {file_path}')
            except Exception as e:
                _logger.warning(
                    f'Failed to load global skill from {file_path}: {str(e)}'
                )

    except Exception as e:
        _logger.debug(f'Failed to find global skill files: {str(e)}')

    return skills


def load_sandbox_skills(sandbox: SandboxInfo) -> list[Skill]:
    """Load skills specific to the sandbox, including exposed ports / urls."""
    if not sandbox.exposed_urls:
        return []
    urls = [url for url in sandbox.exposed_urls if url.name.startswith('WORKER_')]
    if not urls:
        return []
    content_list = [WORK_HOSTS_SKILL]
    for url in urls:
        content_list.append(f'* {url.url} (port {url.port})')
    content = '\n'.join(content_list)
    return [Skill(name='work_hosts', content=content, trigger=None)]


def load_global_skills() -> list[Skill]:
    """Load global skills from OpenHands/skills/ directory.

    Returns:
        List of Skill objects loaded from global skills directory.
        Returns empty list if directory doesn't exist or on errors.
    """
    skill_dir = Path(GLOBAL_SKILLS_DIR)

    # Check if directory exists
    if not skill_dir.exists():
        _logger.debug(f'Global skills directory does not exist: {skill_dir}')
        return []

    try:
        _logger.info(f'Loading global skills from {skill_dir}')

        # Find and load all .md files from the directory
        skills = _find_and_load_global_skill_files(skill_dir)

        _logger.info(f'Loaded {len(skills)} global skills: {[s.name for s in skills]}')

        return skills

    except Exception as e:
        _logger.warning(f'Failed to load global skills: {str(e)}')
        return []


def _determine_repo_root(working_dir: str, selected_repository: str | None) -> str:
    """Determine the repository root directory.

    Args:
        working_dir: Base working directory path
        selected_repository: Repository name (e.g., 'owner/repo') or None

    Returns:
        Path to the repository root directory
    """
    if selected_repository:
        repo_name = selected_repository.split('/')[-1]
        return f'{working_dir}/{repo_name}'
    return working_dir


async def _read_file_from_workspace(
    workspace: AsyncRemoteWorkspace, file_path: str, working_dir: str
) -> str | None:
    """Read file content from remote workspace.

    Args:
        workspace: AsyncRemoteWorkspace to execute commands
        file_path: Path to the file to read
        working_dir: Working directory for command execution

    Returns:
        File content as string, or None if file doesn't exist or read fails
    """
    try:
        result = await workspace.execute_command(
            f'cat {file_path}', cwd=working_dir, timeout=10.0
        )
        if result.exit_code == 0 and result.stdout.strip():
            return result.stdout
        return None
    except Exception as e:
        _logger.debug(f'Failed to read file {file_path}: {str(e)}')
        return None


async def _load_special_files(
    workspace: AsyncRemoteWorkspace, repo_root: str, working_dir: str
) -> list[Skill]:
    """Load special skill files from repository root.

    Loads: .cursorrules, agents.md, agent.md

    Args:
        workspace: AsyncRemoteWorkspace to execute commands
        repo_root: Path to repository root directory
        working_dir: Working directory for command execution

    Returns:
        List of Skill objects loaded from special files
    """
    skills = []
    special_files = ['.cursorrules', 'agents.md', 'agent.md']

    for filename in special_files:
        file_path = f'{repo_root}/{filename}'
        content = await _read_file_from_workspace(workspace, file_path, working_dir)

        if content:
            try:
                # Use simple string path to avoid Path filesystem operations
                skill = Skill.load(path=filename, skill_dir=None, file_content=content)
                skills.append(skill)
                _logger.debug(f'Loaded special file skill: {skill.name}')
            except Exception as e:
                _logger.warning(f'Failed to create skill from {filename}: {str(e)}')

    return skills


async def _find_and_load_skill_md_files(
    workspace: AsyncRemoteWorkspace, skill_dir: str, working_dir: str
) -> list[Skill]:
    """Find and load all .md files from a skills directory in the workspace.

    Args:
        workspace: AsyncRemoteWorkspace to execute commands
        skill_dir: Path to skills directory
        working_dir: Working directory for command execution

    Returns:
        List of Skill objects loaded from the files (excluding README.md)
    """
    skills = []

    try:
        # Find all .md files in the directory
        result = await workspace.execute_command(
            f"find {skill_dir} -type f -name '*.md' 2>/dev/null || true",
            cwd=working_dir,
            timeout=10.0,
        )

        if result.exit_code == 0 and result.stdout.strip():
            file_paths = [
                f.strip()
                for f in result.stdout.strip().split('\n')
                if f.strip() and 'README.md' not in f
            ]

            # Load skills from the found files
            for file_path in file_paths:
                content = await _read_file_from_workspace(
                    workspace, file_path, working_dir
                )

                if content:
                    # Calculate relative path for skill name
                    rel_path = file_path.replace(f'{skill_dir}/', '')
                    try:
                        # Use simple string path to avoid Path filesystem operations
                        skill = Skill.load(
                            path=rel_path, skill_dir=None, file_content=content
                        )
                        skills.append(skill)
                        _logger.debug(f'Loaded repo skill: {skill.name}')
                    except Exception as e:
                        _logger.warning(
                            f'Failed to create skill from {rel_path}: {str(e)}'
                        )

    except Exception as e:
        _logger.debug(f'Failed to find skill files in {skill_dir}: {str(e)}')

    return skills


def _merge_repo_skills_with_precedence(
    special_skills: list[Skill],
    skills_dir_skills: list[Skill],
    microagents_dir_skills: list[Skill],
) -> list[Skill]:
    """Merge repository skills with precedence order.

    Precedence (highest to lowest):
    1. Special files (repo root)
    2. .openhands/skills/ directory
    3. .openhands/microagents/ directory (backward compatibility)

    Args:
        special_skills: Skills from special files in repo root
        skills_dir_skills: Skills from .openhands/skills/ directory
        microagents_dir_skills: Skills from .openhands/microagents/ directory

    Returns:
        Deduplicated list of skills with proper precedence
    """
    # Use a dict to deduplicate by name, with earlier sources taking precedence
    skills_by_name = {}
    for skill in special_skills + skills_dir_skills + microagents_dir_skills:
        # Only add if not already present (earlier sources win)
        if skill.name not in skills_by_name:
            skills_by_name[skill.name] = skill

    return list(skills_by_name.values())


async def load_repo_skills(
    workspace: AsyncRemoteWorkspace,
    selected_repository: str | None,
    working_dir: str,
) -> list[Skill]:
    """Load repository-level skills from the workspace.

    Loads skills from:
    1. Special files in repo root: .cursorrules, agents.md, agent.md
    2. .md files in .openhands/skills/ directory (preferred)
    3. .md files in .openhands/microagents/ directory (for backward compatibility)

    Args:
        workspace: AsyncRemoteWorkspace to execute commands in the sandbox
        selected_repository: Repository name (e.g., 'owner/repo') or None
        working_dir: Working directory path

    Returns:
        List of Skill objects loaded from repository.
        Returns empty list on errors.
    """
    try:
        # Determine repository root directory
        repo_root = _determine_repo_root(working_dir, selected_repository)
        _logger.info(f'Loading repo skills from {repo_root}')

        # Load special files from repo root
        special_skills = await _load_special_files(workspace, repo_root, working_dir)

        # Load .md files from .openhands/skills/ directory (preferred)
        skills_dir = f'{repo_root}/.openhands/skills'
        skills_dir_skills = await _find_and_load_skill_md_files(
            workspace, skills_dir, working_dir
        )

        # Load .md files from .openhands/microagents/ directory (backward compatibility)
        microagents_dir = f'{repo_root}/.openhands/microagents'
        microagents_dir_skills = await _find_and_load_skill_md_files(
            workspace, microagents_dir, working_dir
        )

        # Merge all loaded skills with proper precedence
        all_skills = _merge_repo_skills_with_precedence(
            special_skills, skills_dir_skills, microagents_dir_skills
        )

        _logger.info(
            f'Loaded {len(all_skills)} repo skills: {[s.name for s in all_skills]}'
        )

        return all_skills

    except Exception as e:
        _logger.warning(f'Failed to load repo skills: {str(e)}')
        return []


def merge_skills(skill_lists: list[list[Skill]]) -> list[Skill]:
    """Merge multiple skill lists, avoiding duplicates by name.

    Later lists take precedence over earlier lists for duplicate names.

    Args:
        skill_lists: List of skill lists to merge

    Returns:
        Deduplicated list of skills with later lists overriding earlier ones
    """
    skills_by_name = {}

    for skill_list in skill_lists:
        for skill in skill_list:
            if skill.name in skills_by_name:
                _logger.debug(
                    f'Overriding skill "{skill.name}" from earlier source with later source'
                )
            skills_by_name[skill.name] = skill

    result = list(skills_by_name.values())
    _logger.debug(f'Merged skills: {[s.name for s in result]}')
    return result
